// 版权所有2009 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// go:build aix | darwin | | dragonfly | | | freebsd | | | | | | linux | | netbsd | | openbsd | | solaris | | | windows 

package net

import (
	"context"
	"internal/poll"
	"net/netip"
	"runtime"
	"syscall"
)

// probe probe probe probe probe probe probes IPv4、IPv6和IPv4映射的IPv6通信
// 由IPv6 V6ONLY socket socket选项
// 
// 如果我们只是处理IPv4套接字，我们应该尝试使用IPv4套接字接口吗？只要主机系统理解
// IPv4映射的IPv6，就可以将IPv4映射的IPv6地址传递给
// IPv6接口。这简化了我们的代码，非常简单。不幸的是，我们需要在没有
// IPv6支持的内核上运行。所以，探测内核来找出答案。
func (p *ipStackCapabilities) probe() {
	s, err := sysSocket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)
	switch err {
	case syscall.EAFNOSUPPORT, syscall.EPROTONOSUPPORT:
	case nil:
		poll.CloseFunc(s)
		p.ipv4Enabled = true
	}
	var probes = []struct {
		laddr TCPAddr
		value int
	}{
		// IPv6通信能力
		{laddr: TCPAddr{IP: ParseIP("::1")}, value: 1},
		// IPv4映射的IPv6地址通信能力
		{laddr: TCPAddr{IP: IPv4(127, 0, 0, 1)}, value: 0},
	}
	switch runtime.GOOS {
	case "dragonfly", "openbsd":
		// 最新的蜻蜓BSD和OpenBSD内核不
		// 支持IPv6_V6ONLY=0。他们总是返回一个错误
		// ，我们不需要探测该功能。
		probes = probes[:1]
	}
	for i := range probes {
		s, err := sysSocket(syscall.AF_INET6, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)
		if err != nil {
			continue
		}
		defer poll.CloseFunc(s)
		syscall.SetsockoptInt(s, syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, probes[i].value)
		sa, err := probes[i].laddr.sockaddr(syscall.AF_INET6)
		if err != nil {
			continue
		}
		if err := syscall.Bind(s, sa); err != nil {
			continue
		}
		if i == 0 {
			p.ipv6Enabled = true
		} else {
			p.ipv4MappedIPv6Enabled = true
		}
	}
}

// favoriteAddrFamily返回给定网络、laddr、raddr和模式下
// 的适当地址族。
// 
// 如果模式指示“监听”，laddr是一个通配符，我们假设
// 用户希望与通配符
// 地址族（包括AF INET和AF INET6）和通配符地址
// 建立被动开放连接，如下所示：
// 
// /-a监听通配符通信域，“tcp”或
// “udp”，带有通配符地址：如果平台支持
// IPv6和IPv4映射IPv6通信能力，
// 或不支持IPv4，我们使用双堆栈，AF_INET6和
// IPv6_V6ONLY=0，通配符地址侦听。双栈
// 通配符地址侦听可能会退回到仅IPv6，
// AF_INET6和IPv6_V6ONLY=1，通配符地址侦听。
// 否则，我们更喜欢只使用IPv4的、自动的、通配符地址
// 请听。
// 
// -侦听通配符通信域“tcp”或
// ”udp，IPv4通配符地址：同上。
// 
// -监听通配符通信域“tcp”或
// “udp”，IPv6通配符地址：同上。
// 
// -侦听IPv4通信域，“tcp4”或“udp4”，
// 带有IPv4通配符地址：我们只使用IPv4，AF_INET，
// 通配符地址侦听。
// 
// -侦听IPv6通信域，“tcp6”或“udp6”，
// 带有IPv6通配符地址：我们只使用IPv6，AF_INET6 
// 和IPv6_V6ONLY=1，通配符地址侦听。
// 
// 否则猜测：如果地址是IPv4，则返回AF_INET、
// 否则返回AF_INET6。它还返回一个布尔值，即
// 指定的IPV6_V6ONLY选项。
// 
// 请注意，最新的蜻蜓BSD和OpenBSD内核允许
// 既不允许“net.inet6.ip6.v6only=1”更改，也不允许IPPROTO_IPV6级别
// IPV6_v6only套接字选项设置。
func favoriteAddrFamily(network string, laddr, raddr sockaddr, mode string) (family int, ipv6only bool) {
	switch network[len(network)-1] {
	case '4':
		return syscall.AF_INET, false
	case '6':
		return syscall.AF_INET6, true
	}

	if mode == "listen" && (laddr == nil || laddr.isWildcard()) {
		if supportsIPv4map() || !supportsIPv4() {
			return syscall.AF_INET6, false
		}
		if laddr == nil {
			return syscall.AF_INET, false
		}
		return laddr.family(), false
	}

	if (laddr == nil || laddr.family() == syscall.AF_INET) &&
		(raddr == nil || raddr.family() == syscall.AF_INET) {
		return syscall.AF_INET, false
	}
	return syscall.AF_INET6, false
}

func internetSocket(ctx context.Context, net string, laddr, raddr sockaddr, sotype, proto int, mode string, ctrlFn func(string, string, syscall.RawConn) error) (fd *netFD, err error) {
	if (runtime.GOOS == "aix" || runtime.GOOS == "windows" || runtime.GOOS == "openbsd") && mode == "dial" && raddr.isWildcard() {
		raddr = raddr.toLocal(net)
	}
	family, ipv6only := favoriteAddrFamily(net, laddr, raddr, mode)
	return socket(ctx, net, family, sotype, proto, ipv6only, laddr, raddr, ctrlFn)
}

func ipToSockaddrInet4(ip IP, port int) (syscall.SockaddrInet4, error) {
	if len(ip) == 0 {
		ip = IPv4zero
	}
	ip4 := ip.To4()
	if ip4 == nil {
		return syscall.SockaddrInet4{}, &AddrError{Err: "non-IPv4 address", Addr: ip.String()}
	}
	sa := syscall.SockaddrInet4{Port: port}
	copy(sa.Addr[:], ip4)
	return sa, nil
}

func ipToSockaddrInet6(ip IP, port int, zone string) (syscall.SockaddrInet6, error) {
	// 通常，IP通配符地址是
	// “0.0.0.0”或“：”，表示整个IP地址
	// 空间。由于某些历史原因，它用于在IP节点的某些操作上指定“任何可用地址”。
	// 
	// 当IP节点支持IPv4映射的IPv6地址时，
	// 我们允许侦听器通过指定
	// 地址。
	// IPv6通配符地址来侦听两个IP寻址空间的通配符
	if len(ip) == 0 || ip.Equal(IPv4zero) {
		ip = IPv6zero
	}
	// 我们接受任何IPv6地址，包括IPv4映射的
	// IPv6地址。
	ip6 := ip.To16()
	if ip6 == nil {
		return syscall.SockaddrInet6{}, &AddrError{Err: "non-IPv6 address", Addr: ip.String()}
	}
	sa := syscall.SockaddrInet6{Port: port, ZoneId: uint32(zoneCache.index(zone))}
	copy(sa.Addr[:], ip6)
	return sa, nil
}

func ipToSockaddr(family int, ip IP, port int, zone string) (syscall.Sockaddr, error) {
	switch family {
	case syscall.AF_INET:
		sa, err := ipToSockaddrInet4(ip, port)
		if err != nil {
			return nil, err
		}
		return &sa, nil
	case syscall.AF_INET6:
		sa, err := ipToSockaddrInet6(ip, port, zone)
		if err != nil {
			return nil, err
		}
		return &sa, nil
	}
	return nil, &AddrError{Err: "invalid address family", Addr: ip.String()}
}

func addrPortToSockaddrInet4(ap netip.AddrPort) (syscall.SockaddrInet4, error) {
	// ipToSockaddrInet4对零长度切片有特殊处理。
	// 我们没有，因为netip没有通用零IP地址的概念。
	addr := ap.Addr()
	if !addr.Is4() {
		return syscall.SockaddrInet4{}, &AddrError{Err: "non-IPv4 address", Addr: addr.String()}
	}
	sa := syscall.SockaddrInet4{
		Addr: addr.As4(),
		Port: int(ap.Port()),
	}
	return sa, nil
}

func addrPortToSockaddrInet6(ap netip.AddrPort) (syscall.SockaddrInet6, error) {
	// ipToSockaddrInet6对零长度切片有特殊处理。
	// 我们没有，因为netip没有通用零IP地址的概念。
	addr := ap.Addr()
	if !addr.Is6() {
		return syscall.SockaddrInet6{}, &AddrError{Err: "non-IPv6 address", Addr: addr.String()}
	}
	sa := syscall.SockaddrInet6{
		Addr:   addr.As16(),
		Port:   int(ap.Port()),
		ZoneId: uint32(zoneCache.index(addr.Zone())),
	}
	return sa, nil
}
